/**
 * @file bsp_ethernet.c
 * @author zdk
 * @brief 以太网接口抽象层
 * @version 0.1
 * @date 2021-07-08
 * 
 * BSD 3-Clause License
 * 
 * Copyright (c) 2021, water_zhang
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * 
 * * Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */

#include "bsp_ethernet.h"
#include "enc28j60.h"
#include "stm32f4xx.h"
#include "rtthread.h"

/* 网卡中断引脚 */
#define IRQ_PORT GPIOG
#define IRQ_PIN GPIO_PIN_10

/* 以太网MAC地址 */
static uint8_t eth_mac_addr[6] = {0xC7, 0x76, 0xA4, 0x4E, 0x56, 0x9B};

/* 互斥锁 */
struct rt_mutex eth_mutex;

//储存用户回调指针与自定义参数指针
ethernet_cb usr_cb = (void *)0;
void *usr_param = (void *)0;

/* 中断接收引脚初始化 */
static void Bsp_Ethernet_EnableIRQ(void)
{
    GPIO_InitTypeDef init;

    __HAL_RCC_GPIOG_CLK_ENABLE();

    //初始化中断对应引脚
    init.Mode = GPIO_MODE_IT_FALLING;
    init.Pin = IRQ_PIN;
    init.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(IRQ_PORT, &init);

    //使能中断
    HAL_NVIC_SetPriority(EXTI15_10_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}

/* 中断接收回调 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (IRQ_PIN == GPIO_Pin)
    {
        /* 调用用户自定义的回调函数 */
        if (usr_cb)
        {
            usr_cb(usr_param);
        }
    }
}

/* 初始化以太网网卡 */
eth_result Bsp_Ethernet_Init(void)
{
    /* 初始化网卡 */
    if (ENC28J60_Init(eth_mac_addr))
    {
        /* 初始化互斥锁 */
        if (RT_EOK != rt_mutex_init(&eth_mutex, "eth", RT_IPC_FLAG_PRIO))
        {
            return ETH_ERR;
        }

        /* 初始化接收网卡中断的引脚 */
        Bsp_Ethernet_EnableIRQ();

        return ETH_OK;
    }
    else
    {
        return ETH_ERR;
    }
}

/* 获取以太网地址 */
eth_result Bsp_Ethernet_GetAddr(uint8_t *addr, uint8_t len)
{
    if (!addr || len < 6)
    {
        return ETH_ERR;
    }

    addr[0] = eth_mac_addr[0];
    addr[1] = eth_mac_addr[1];
    addr[2] = eth_mac_addr[2];
    addr[3] = eth_mac_addr[3];
    addr[4] = eth_mac_addr[4];
    addr[5] = eth_mac_addr[5];

    return ETH_OK;
}

/* 发送数据包 */
eth_result Bsp_Ethernet_Transmit(uint8_t *pBuff, uint16_t len)
{
    uint8_t ret;

    if (!pBuff)
    {
        return ETH_ERR;
    }

    rt_mutex_take(&eth_mutex, RT_WAITING_FOREVER);
    ret = ENC28J60_SendPacket(pBuff, len);
    rt_mutex_release(&eth_mutex);

    if (ret == 1)
    {
        return ETH_OK;
    }
    else if (ret == 2)
    {
        return ETH_BUSY;
    }
    else
    {
        return ETH_ERR;
    }
}

/* 从网卡读取一个数据包 */
eth_result Bsp_Ethernet_Receive(uint8_t *pBuff, uint16_t len, uint16_t *byte_read)
{
    uint16_t enc28j60_ret;
    eth_result eth_ret;

    if (!pBuff || !byte_read)
    {
        eth_ret = ETH_ERR;
    }

    rt_mutex_take(&eth_mutex, RT_WAITING_FOREVER);
    enc28j60_ret = ENC28J60_ReceivePacket(pBuff, len);
    rt_mutex_release(&eth_mutex);

    if (1 == enc28j60_ret)
    {
        /* 返回1说明读取中发送错误，一般为传入的缓冲区太小，装不下一个数据包 */
        *byte_read = 0;
        eth_ret = ETH_ERR;
    }
    else if (0 == enc28j60_ret)
    {
        /* 返回NODATA说明当前没有数据包可以读取 */
        *byte_read = 0;
        eth_ret = ETH_NODATA;
    }
    else
    {
        *byte_read = enc28j60_ret;
        eth_ret = ETH_OK;
    }

    return eth_ret;
}

/**
 * @brief 获取一些网卡内的寄存器信息
 * 
 * @param info 信息存储结构体
 */
eth_result Bsp_Ethernet_GetInfo(eth_info *info)
{
    if (!info)
    {
        return ETH_ERR;
    }

    rt_mutex_take(&eth_mutex, RT_WAITING_FOREVER);

    __ENC28J60_RCR_ETH(REG_NOBANK_ECON1, &info->nobank.ECON1);
    __ENC28J60_RCR_ETH(REG_NOBANK_ECON2, &info->nobank.ECON2);
    __ENC28J60_RCR_ETH(REG_NOBANK_EIE, &info->nobank.EIE);
    __ENC28J60_RCR_ETH(REG_NOBANK_EIR, &info->nobank.EIR);
    __ENC28J60_RCR_ETH(REG_NOBANK_ESTAT, &info->nobank.ESTAT);

    __ENC28J60_SET_BANK(1);
    __ENC28J60_RCR_ETH(REG_BANK1_EPKTCNT, &info->bank1.EPKTCNT);
    __ENC28J60_RCR_ETH(REG_BANK1_ERXFCON, &info->bank1.ERXFCON);

    __ENC28J60_SET_BANK(2);
    __ENC28J60_RCR_MAC_MII(REG_BANK2_MACON1, &info->bank2.MACON1);
    __ENC28J60_RCR_MAC_MII(REG_BANK2_MACON2, &info->bank2.MACON2);
    __ENC28J60_RCR_MAC_MII(REG_BANK2_MACON3, &info->bank2.MACON3);
    __ENC28J60_RCR_MAC_MII(REG_BANK2_MACON4, &info->bank2.MACON4);

    info->irq_level = (uint8_t)HAL_GPIO_ReadPin(IRQ_PORT, IRQ_PIN);

    rt_mutex_release(&eth_mutex);

    return ETH_OK;
}

/**
 * @brief 注册中断回调
 * 
 * @param cb 回调函数指针
 * @param param 自定义参数
 */
void Bsp_Ethernet_RegisterRecvCallback(ethernet_cb cb, void *param)
{
    rt_mutex_take(&eth_mutex, RT_WAITING_FOREVER);
    usr_param = param;
    usr_cb = cb;
    rt_mutex_release(&eth_mutex);
}

/**
 * @brief 使能INT引脚的中断输出功能
 * 
 * @param flag 要使能的中断，可以为如下值相或，或者0xFF打开所有中断：
 *              ETH_FLAG_PKT     接收数据包待处理中断允许位 
 *              ETH_FLAG_DMA     DMA 中断允许位 
 *              ETH_FLAG_LINK    连接状态改变中断允许位 
 *              ETH_FLAG_TX      发送中断允许位 
 *              ETH_FLAG_WOL     WOL 中断允许位 
 *              ETH_FLAG_TXER    发送错误中断允许位 
 *              ETH_FLAG_RXER    接收错误中断允许位 
 */
void Bsp_Ethernet_EnableINT(uint8_t flag)
{
    rt_mutex_take(&eth_mutex, RT_WAITING_FOREVER);
    ENC28J60_EnableINT(flag);
    rt_mutex_release(&eth_mutex);
}

/**
 * @brief 失能INT引脚的中断输出功能
 * 
 * @param flag 要失能的中断，可以为如下值相或，或者0xFF关闭所有中断：
 *              ETH_FLAG_PKT     接收数据包待处理中断允许位 
 *              ETH_FLAG_DMA     DMA 中断允许位 
 *              ETH_FLAG_LINK    连接状态改变中断允许位 
 *              ETH_FLAG_TX      发送中断允许位 
 *              ETH_FLAG_WOL     WOL 中断允许位 
 *              ETH_FLAG_TXER    发送错误中断允许位 
 *              ETH_FLAG_RXER    接收错误中断允许位 
 */
void Bsp_Ethernet_DisableINT(uint8_t flag)
{
    rt_mutex_take(&eth_mutex, RT_WAITING_FOREVER);
    ENC28J60_DisableINT(flag);
    rt_mutex_release(&eth_mutex);
}

/**
 * @brief 获取当前触发中断的中断标志位
 * @note  如果使能了以太网中断，则务必确保在中断发生后调用这个函数，
 *       否则INT引脚会一直保持为低电平，导致后续中断丢失，但是不建
 *       议在中断中直接调用，因为涉及到SPI操作，可能发生中断时正好
 *       在进行SPI操作，而SPI外设可不是“可重入”的。
 * 
 * @return uint8_t 返回所有生效的中断标志位，
 *                 其值可以为以下值之一，或者它们的或结果，
 *                 如果为这些值的或结果，说明有多个中断发生了
 *                  ETH_FLAG_PKT 
 *                  ETH_FLAG_LINK    
 *                  ETH_FLAG_TX      
 *                  ETH_FLAG_TXER    
 *                  ETH_FLAG_RXER     
 */
uint8_t Bsp_Ethernet_GetInterruptFlag(void)
{
    uint8_t irq;
    rt_mutex_take(&eth_mutex, RT_WAITING_FOREVER);
    irq = ENC28J60_GetInterruptFlag();
    rt_mutex_release(&eth_mutex);
    return irq;
}
